﻿using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Daybreak.Core.Rendering
{

    public partial class Geometry
    {
        public static class Factory
        {

            /// <summary>
            /// A face on the sphere (3 or 4 indices)
            /// </summary>
            class SphereFace
            {
                /// <summary>
                /// Indices on the sphere
                /// </summary>
                public int v1;
                public int v2;
                public int v3;
                public int? v4;

                public int VertexCount
                {
                    get
                    {
                        if (v4.HasValue)
                            return 4;

                        return 3;
                    }
                }

                public SphereFace(int p1, int p2, int p3)
                {
                    v1 = p1;
                    v3 = p2;
                    v2 = p3;
                }
                public SphereFace(int p1, int p2, int p3, int p4)
                {
                    v1 = p1;
                    v3 = p2;
                    v2 = p3;
                    v4 = p4;
                }
            }

            class SphereGeometry
            {
                internal List<SphereFace> faces;
                internal VertexPositionNormalTexture[] vertices;

                public int FacesCount
                {
                    get
                    {
                        int result = 0;

                        foreach (SphereFace face in faces)
                        {
                            if (face.VertexCount == 3)
                                result += 1;
                            else
                                result += 2;
                        }

                        return result;
                    }
                }
            }

            public static void CreateSphereMesh(float radius, int nLat, int nLong, Geometry geom, IMaterial mat)
            {
                geom.Mesh = new Mesh();

                SphereGeometry sphere = BuildSphere(radius, nLat, nLong, Vector3.Right, Vector3.Up, Vector3.Backward);

                // set vertex count and index count
                int nbrVertices = sphere.vertices.Length;
                int nbrIndices = (2 * nLat * (nLong + 1));

                //geom.primitiveType = PrimitiveType.TriangleList;
                //geom.primitivesCount = nbrIndices - 2;

                geom.Mesh.Vertices.SetArrays(sphere.vertices);

                VertexPositionNormalTexture[] vertices = sphere.vertices;
                List<int> indexes = new List<int>(2 * nLat * (nLong + 1));

                foreach (SphereFace face in sphere.faces)
                {
                    if (face.VertexCount == 3)
                    {
                        indexes.Add(face.v3);
                        indexes.Add(face.v2);
                        indexes.Add(face.v1);
                    }

                    else
                    {
                        indexes.Add(face.v1);
                        indexes.Add(face.v3);
                        indexes.Add(face.v2);

                        indexes.Add(face.v2);
                        indexes.Add(face.v4.Value);
                        indexes.Add(face.v1);

                    }
                }

                int[] indBuff = indexes.ToArray();

                nbrIndices = indBuff.Length;

                Drawable.Triangles tris = new Drawable.Triangles(indBuff);
                tris.Inputs = new Drawable.Topology.InputOffset[3];
                tris.Inputs[0].SetUsage<Vector3>(VertexElementUsage.Position, 0, 0);
                tris.Inputs[1].SetUsage<Vector3>(VertexElementUsage.Normal, 0, 0);
                tris.Inputs[2].SetUsage<Vector2>(VertexElementUsage.TextureCoordinate, 0, 0);

                tris.WindingOrder = CullMode.CullCounterClockwiseFace;

                geom.Mesh.AddMeshPart(tris, mat);

            }

            private static SphereGeometry BuildSphere(float radius, int nLat, int nLong, Vector3 axis1, Vector3 axis2, Vector3 axis3)
            {
                if (nLong < 1)
                {
                    nLong = 1;
                }
                if (nLat < 3)
                {
                    nLat = 1;
                }
                if (radius < 0f)
                {
                    radius = -radius;
                }
                nLong++;
                nLat += 2;
                int vertexCount = nLong * nLat;

                VertexPositionNormalTexture[] verts = new VertexPositionNormalTexture[vertexCount];
                List<SphereFace> faces = new List<SphereFace>();

                for (int i = 0; i < nLat; i++)
                {
                    float latAngle = (float)(((Math.PI * i) / ((float)(nLat - 1))) - (Math.PI / 2f));
                    float sin = (float)(radius * Math.Sin((double)latAngle));
                    float r2 = (float)Math.Sqrt((double)Math.Abs((float)((radius * radius) - (sin * sin))));

                    for (int j = 0; j < nLong; j++)
                    {
                        float longAngle = (float)((2f * Math.PI) * j) / ((float)(nLong - 1));
                        float x0 = (float)(r2 * Math.Cos((double)longAngle));
                        float y0 = (float)(r2 * Math.Sin((double)longAngle));

                        int index = j + (i * nLong);

                        if (index > vertexCount)
                        {
                            index = vertexCount - 1;
                        }

                        verts[index].Position.X = ((x0 * axis1.X) + (y0 * axis2.X)) + (sin * axis3.X);
                        verts[index].Position.Y = ((x0 * axis1.Y) + (y0 * axis2.Y)) + (sin * axis3.Y);
                        verts[index].Position.Z = ((x0 * axis1.Z) + (y0 * axis2.Z)) + (sin * axis3.Z);

                        float longCoeff = ((float)j) / ((float)(nLong - 1));
                        float latCoeff = ((float)i) / ((float)(nLat - 1));

                        if (i == 0)
                        {
                            longCoeff -= 0.5f / ((float)nLong);
                        }
                        else if (i == (nLat - 1))
                        {
                            longCoeff += 0.5f / ((float)nLong);
                        }

                        verts[index].TextureCoordinate.X = 1f - longCoeff;
                        verts[index].TextureCoordinate.Y = latCoeff;

                        Vector3 vector = verts[index].Position;
                        vector.Normalize();
                        verts[index].Normal = vector;
                    }
                }

                for (int k = 1; k < (nLat - 2); k++)
                {
                    for (int m = 0; m < (nLong - 1); m++)
                    {
                        int v4 = m + (k * nLong);
                        int v1 = m + ((k + 1) * nLong);
                        int v3 = ((m + 1) % nLong) + (k * nLong);
                        int v2 = ((m + 1) % nLong) + ((k + 1) * nLong);

                        SphereFace item = new SphereFace(v1, v2, v3, v4);
                        faces.Add(item);
                    }
                }
                for (int n = 0; n < (nLong - 1); n++)
                {
                    int v3 = n;
                    int v2 = (n + 1) + nLong;
                    int v1 = n + nLong;
                    SphereFace face2 = new SphereFace(v1, v2, v3);

                    faces.Add(face2);
                }
                for (int n = 0; n < (nLong - 1); n++)
                {
                    int v3 = n + ((nLat - 1) * nLong);
                    int v2 = n + ((nLat - 2) * nLong);
                    int v1 = (n + 1) + ((nLat - 2) * nLong);
                    SphereFace face3 = new SphereFace(v1, v2, v3);
                    faces.Add(face3);
                }

                SphereGeometry sphere = new SphereGeometry();
                sphere.faces = faces;
                sphere.vertices = verts;

                return sphere;
            }

            public static void CreateBoxMesh(float x, float y, float z, Geometry geom, IMaterial mat)
            {
                geom.Mesh = new Mesh();
                //geom.Mesh.RequestGeometrySlot();
                Vector3[] posArr = new Vector3[8];

                #region positions

                posArr[0] = new Vector3(-x, -y, -z);
                posArr[1] = new Vector3(x, -y, -z);
                posArr[2] = new Vector3(-x, y, -z);
                posArr[3] = new Vector3(x, y, -z);
                posArr[4] = new Vector3(-x, -y, z);
                posArr[5] = new Vector3(x, -y, z);
                posArr[6] = new Vector3(-x, y, z);
                posArr[7] = new Vector3(x, y, z);

                #endregion

                Vector3[] normArr = new Vector3[24];

                #region normals
                normArr[0] = new Vector3(0, 0, -1);
                normArr[1] = new Vector3(0, 0, -1);
                normArr[2] = new Vector3(0, 0, -1);
                normArr[3] = new Vector3(0, 0, -1);

                normArr[4] = new Vector3(0, 0, 1);
                normArr[5] = new Vector3(0, 0, 1);
                normArr[6] = new Vector3(0, 0, 1);
                normArr[7] = new Vector3(0, 0, 1);

                normArr[8] = new Vector3(0, -1, 0);
                normArr[9] = new Vector3(0, -1, 0);
                normArr[10] = new Vector3(0, -1, 0);
                normArr[11] = new Vector3(0, -1, 0);

                normArr[12] = new Vector3(1, 0, 0);
                normArr[13] = new Vector3(1, 0, 0);
                normArr[14] = new Vector3(1, 0, 0);
                normArr[15] = new Vector3(1, 0, 0);

                normArr[16] = new Vector3(0, 1, 0);
                normArr[17] = new Vector3(0, 1, 0);
                normArr[18] = new Vector3(0, 1, 0);
                normArr[19] = new Vector3(0, 1, 0);

                normArr[20] = new Vector3(-1, 0, 0);
                normArr[21] = new Vector3(-1, 0, 0);
                normArr[22] = new Vector3(-1, 0, 0);
                normArr[23] = new Vector3(-1, 0, 0);
                #endregion

                Vector2[] texArr = new Vector2[12];

                #region tex coords
                texArr[0] = new Vector2(0, 0);
                texArr[1] = new Vector2(1, 0);
                texArr[2] = new Vector2(0, 1);
                texArr[3] = new Vector2(1, 1);
                texArr[4] = new Vector2(0, 0);
                texArr[5] = new Vector2(1, 0);
                texArr[6] = new Vector2(0, 1);
                texArr[7] = new Vector2(1, 1);
                texArr[8] = new Vector2(0, 0);
                texArr[9] = new Vector2(1, 0);
                texArr[10] = new Vector2(0, 1);
                texArr[11] = new Vector2(1, 1);
                #endregion

                geom.Mesh.Vertices.SetArray(posArr, VertexElementUsage.Position, 0);
                geom.Mesh.Vertices.SetArray(normArr, VertexElementUsage.Normal, 0);
                geom.Mesh.Vertices.SetArray(texArr, VertexElementUsage.TextureCoordinate, 1);

                #region inices
                int[] primitiveIdices = new int[] 
            { 
                0, 0, 9,     2, 1, 11,     3, 2, 10,     3, 2, 10, 
                1, 3, 8,     0, 0, 9,      4, 4,  8,     5, 5, 9, 
                7, 6, 11,    7, 6, 11,     6, 7, 10,     4, 4, 8, 
                
                0, 8, 4,     1, 9, 5,      5, 10, 7,     5, 10, 7,
                4, 11,6,     0, 8, 4,      1, 12, 0,     3, 13, 1,
                7, 14,3,     7, 14,3,      5, 15, 2,     1, 12, 0,
            
                3, 16,4,     2, 17,5,      6, 18, 7,     6, 18, 7, 
                7, 19,6,     3, 16,4,      2, 20, 0,     0, 21, 1, 
                4, 22,3,     4, 22,3,      6, 23, 2,     2, 20, 0
            };
                #endregion

                Drawable.Triangles tris = new Drawable.Triangles(primitiveIdices);
                tris.Inputs = new Drawable.Topology.InputOffset[3];
                tris.Inputs[0].SetUsage<Vector3>(VertexElementUsage.Position, 0, 0);
                tris.Inputs[1].SetUsage<Vector3>(VertexElementUsage.Normal, 1, 0);
                tris.Inputs[2].SetUsage<Vector2>(VertexElementUsage.TextureCoordinate, 2, 1);

                geom.Mesh.AddMeshPart(tris, mat);

            }

        }
    }
}
